package com.zoulj.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zoulj.error.BusinessException;
import com.zoulj.error.CommonError;
import com.zoulj.error.EmBusinessError;
import com.zoulj.mapper.ItemStockMapper;
import com.zoulj.mapper.OrderInfoMapper;
import com.zoulj.pojo.R;
import com.zoulj.pojo.bo.OrderParamBO;
import com.zoulj.pojo.entity.OrderInfoDO;
import com.zoulj.service.OrderInfoService;
import com.zoulj.util.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @Description: 订单记录表
 * @Date: 2021-06-27
 */
@Service
@Slf4j
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfoDO> implements OrderInfoService {

    @Autowired
    private ItemStockMapper itemStockMapper;

    @Autowired
    private OrderInfoMapper orderInfoMapper;

    @Autowired
    private Redisson redisson;

    @Autowired
    private IdWorker idWorker;

    /**
     * REQUIRED：使用当前事务,若无则新建一个事务否则加入当前事务,子方法必须运行在一个事务中
     * SUPPORTS：当前有事务，则使用事务，否则不使用事务
     * MANDATORY：该传播属性强制必须存在一个事务，不存在就抛出异常
     * REQUIRES_NEW: 当前有事务，则挂起该事务，并且自己创建一个新的事务给自己使用；如果当前无事务同 REQUIRED
     * NOT_SUPPORTED: 当前有事务，则把事务挂起，自己不适用事务去运行数据库操作
     * NEVER: 当前有事务 ->抛出异常
     * NESTED:
     *     如果当前有事务，则开启子事务（嵌套事务），嵌套事务是独立提交或者回滚；
     *     如果当前没有事务，则同 REQUIRED。
     *     但是如果主事务提交，则会携带子事务一起提交。
     *     如果主事务回滚，则子事务会一起回滚。相反，子事务异常，则父事务可以回滚或不回滚。
     *     举例：领导决策不对，老板怪罪，领导带着小弟一同受罪。小弟出了差错，领导可以推卸责任。
     * @param orderParamBO
     * @return
     * @throws Exception
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public R generateOrder(OrderParamBO orderParamBO) throws Exception {
        Long orderId = -1l;
        String lockey = orderParamBO.getItemCode() + "_001";
        RLock rLock = redisson.getLock(lockey);
        try {
            //扣减库存表
            //多线程问题采用分布式锁
            rLock.lock(30, TimeUnit.SECONDS);
            int result = itemStockMapper.decrease(orderParamBO.getItemCode(),
                    orderParamBO.getCount());
            if (result > 0) {
                log.info("{} 成功扣减：{}", orderParamBO.getItemCode(), orderParamBO.getCount());
                //生成订单
                orderId = idWorker.nextId();
                log.info("订单：{} 生成成功",orderId);
                //插入订单表
                OrderInfoDO orderInfo = OrderInfoDO.builder().id(orderId)
                        .userId(orderParamBO.getUserId())
                        .itemCode(orderParamBO.getItemCode())
                        .orderPrice(orderParamBO.getPrice())
                        .gmtCreate(new Date()).build();
                orderInfoMapper.insert(orderInfo);
            } else {
                log.info("库存不足,扣减失败");
            }

            //模拟异常的事务回滚
            //int i = 9/0;
        } catch (Exception e) {
            log.error("order error:{}", e);
            throw new BusinessException(EmBusinessError.ORDER_ERROR);
        }finally {
            rLock.unlock();
        }
        return new R(orderId);
    }
}
